package ads.server;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import spread.SpreadException;
import ads.exception.PlayingGameException;
import ads.exception.ServerRemoteException;
import ads.exception.ServerSpreadException;
import ads.remote.ClientInterface;
import ads.remote.ServerInterface;
import ads.util.GameInfo;
import ads.util.PlayerInfo;
import ads.util.ServerConfig;
import at.falb.games.alcatraz.api.Player;

public class ServerImpl extends SpreadMember implements ServerInterface {

	private static final Logger log = Logger.getLogger(ServerImpl.class
			.getName());

	public ServerImpl(ServerConfig config, ServerMain gui)
			throws ServerSpreadException, ServerRemoteException {
		super(config, gui);
		initRMI();
		log.info("Server " + config.getServerName() + "is ready");
	}

	private void initRMI() throws ServerRemoteException {
		log.info("Server " + config.getServerName() + " Starting...");
		if (System.getSecurityManager() == null) {
			System.setSecurityManager(new SecurityManager());
		}
		log.debug("set SecurityManager");
		try {
			ServerInterface stub = (ServerInterface) UnicastRemoteObject
					.exportObject(this, config.getServerPort());
			log.debug("UnicastRemoteObject exportObject at port:"
					+ config.getServerPort());

			Registry registry = LocateRegistry.getRegistry(config
					.getRegstryHost(), config.getRegstryPort());
			log.debug("LocateRegistry getRegistry at:"
					+ config.getRegstryHost() + ":" + config.getRegstryPort());

			registry.rebind(config.getServerName(), stub);
			log.debug("rebind " + config.getServerName());
			log.info("Server " + config.getServerName() + " registered");
			log.info("Server " + config.getServerName() + "Started");
		} catch (RemoteException e) {
			log.error("RMI register cause exception", e);
			throw new ServerRemoteException(e);
		} catch (Exception e) {
			log.error("RMI register cause exception", e);
			throw new ServerRemoteException(e);
		}
	}

	@Override
	public GameInfo createGame(Player player, int countOfPlayers)
			throws RemoteException, PlayingGameException {
		log.info("create a new game by player:" + player
				+ " , count of Player:" + countOfPlayers);
		GameInfo game = new GameInfo();
		game.setCountOfPlayer(countOfPlayers);
		ArrayList<PlayerInfo> players = new ArrayList<PlayerInfo>();
		players.add(getPlayerInfo(player));
		game.setPlayers(players);
		synchronized (games) {
			Integer id = 0;
			for (Integer key : games.keySet()) {
				if (key > id) {
					id = key;
				}
			}
			id++;
			game.setId(id);
			games.put(id, game);
		}
		try {
			Update(UpdateOperator.add, game);
		} catch (SpreadException e) {
			e.printStackTrace();
			log.error("BackupServer Update Exception", e);
		}
		return game;
	}

	private PlayerInfo getPlayerInfo(Player player) {
		for (PlayerInfo p : players) {
			if (p.getPlayer().equals(player)) {
				return p;
			}
		}
		return null;
	}

	@Override
	public GameInfo joinGame(PlayerInfo player, Integer gameid)
			throws RemoteException, PlayingGameException {
		log.info("player " + player + " join game. game id:" + gameid);
		GameInfo game = games.get(gameid);
		synchronized (game) {
			if (game == null)
				throw new PlayingGameException("game not exist ");
			if (game.getCountOfPlayer() == game.getPlayers().size()) {
				throw new PlayingGameException("game is full");
			}
			game.getPlayers().add(player);
			try {
				Update(UpdateOperator.update, game);
			} catch (SpreadException e) {
				e.printStackTrace();
				log.error("BackupServer Update Exception", e);
			}
			log.debug("number of players:" + game.getCountOfPlayer()
					+ " actual number now:" + game.getPlayers().size());
			if (game.getCountOfPlayer() == game.getPlayers().size()) {
				log.debug("number of joined is equal to allowed number");
				new StartGameThread(this, game).start();
			}
		}
		return game;
	}

	@Override
	public List<GameInfo> listAllGames() throws RemoteException {
		log.info("list all games");
		List<GameInfo> gameList = new ArrayList<GameInfo>();
		gameList.addAll(games.values());
		log.debug(gameList);
		return gameList;
	}

	@Override
	public void logout(PlayerInfo player) throws RemoteException {
		log.info("user logout .player:" + player);
		synchronized (players) {
			if (players.contains(player)) {
				players.remove(player);
				try {
					Update(UpdateOperator.delete, player);
				} catch (SpreadException e) {
					e.printStackTrace();
					log.error("BackupServer Update Exception", e);
				}
			}
		}
	}

	@Override
	public void register(Player player, ClientInterface clientStub)
			throws RemoteException, PlayingGameException {
		log.info("user login.player:" + player + ",clientStub" + clientStub);
		PlayerInfo pi = new PlayerInfo(player, clientStub);
		synchronized (players) {
			if (players.contains(pi)) {
				log.info("user already exists.");
				throw new PlayingGameException("user already exists.");
			} else {
				players.add(pi);
				try {
					Update(UpdateOperator.add, pi);
				} catch (SpreadException e) {
					e.printStackTrace();
					log.error("BackupServer Update Exception", e);
				}
			}
		}
	}

	@Override
	public void relogin(Player player, ClientInterface clientStub)
			throws RemoteException {
		log.info("user relogin.player:" + player + ",clientStub" + clientStub);
		PlayerInfo pi = new PlayerInfo(player, clientStub);
		boolean recovery = false;
		synchronized (players) {
			if (players.contains(pi)) {
				players.remove(pi);
				players.add(pi);
				recovery = true;
				try {
					Update(UpdateOperator.update, pi);
				} catch (SpreadException e) {
					e.printStackTrace();
					log.error("BackupServer Update Exception", e);
				}
			} else {
				players.add(pi);
				try {
					Update(UpdateOperator.update, pi);
				} catch (SpreadException e) {
					e.printStackTrace();
					log.error("BackupServer Update Exception", e);
				}
			}
		}
		if (recovery) {
			synchronized (games) {
				for (GameInfo game : games.values()) {
					log.debug("game:"+game);
					if (game.hasPlayer(pi)) {
						// try {
						game.updatePlayer(pi);
						
						//update clients
//						log.debug("recovery for client"+pi.getStub()+" for game "+game);
//						pi.getStub().recovery(game);
						
						try {
							Update(UpdateOperator.update, game);
						} catch (SpreadException e) {
							e.printStackTrace();
							log.error("BackupServer Update Exception", e);
						}
						// } catch (RemoteException e) {
						// log.fatal("RemoteException,error by recovery game",
						// e);
						// }
						break;
					}
				}
			}
			
		}
	}

	public void stopServer() {
		log.info("stop server...");
		try {
			leaveFromGroup();
		} catch (SpreadException e) {
			e.printStackTrace();
			log.error(e);
		}
		try {
			log.info("unregister remote object");
			// Thread.sleep(1000L);
			Registry registry = LocateRegistry.getRegistry(config
					.getRegstryHost(), config.getRegstryPort());
			registry.unbind(config.getServerName());
		} catch (Exception e) {
			log.error(e);
		}
		log.info("System.exit");
		System.exit(1);
	}

	@Override
	public void accessionToPrimaryServer() {
		log.info("accessionToPrimaryServer, forward client connection");
		new AccessionToPrimaryServerThread(this).start();
	}

	@Override
	public void requestRecovery(PlayerInfo pi) throws RemoteException {
		// TODO Auto-generated method stub
		synchronized (games) {
			for (GameInfo game : games.values()) {
				log.debug("game:"+game);
				if (game.hasPlayer(pi)) {
					// try {
					game.updatePlayer(pi);
					
					//update clients
					log.debug("recovery for client"+pi.getStub()+" for game "+game);
					pi.getStub().recovery(game);
					
					try {
						Update(UpdateOperator.update, game);
					} catch (SpreadException e) {
						e.printStackTrace();
						log.error("BackupServer Update Exception", e);
					}
					// } catch (RemoteException e) {
					// log.fatal("RemoteException,error by recovery game",
					// e);
					// }
					break;
				}
			}
		}
		
	}

}
